package core.proto.vis;

import core.proto.vis.action.TreeRootAction;
import core.proto.vis.action.assignment.EdgeColourAction;
import core.proto.vis.action.assignment.TextColourAction;
import core.proto.vis.action.layout.AggregateLayout;
import core.proto.vis.controls.*;
import core.util.logging.UserLogWriter;
import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.ItemAction;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.PolarLocationAnimator;
import prefuse.action.animate.QualityControlAnimator;
import prefuse.action.animate.VisibilityAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.assignment.DataSizeAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.filter.GraphDistanceFilter;
import prefuse.action.layout.CollapsedSubtreeLayout;
import prefuse.action.layout.graph.RadialTreeLayout;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.ControlAdapter;
import prefuse.controls.FocusControl;
import prefuse.controls.HoverActionControl;
import prefuse.data.*;
import prefuse.data.event.TupleSetListener;
import prefuse.data.io.GraphMLReader;
import prefuse.data.query.SearchQueryBinding;
import prefuse.data.search.PrefixSearchTupleSet;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.*;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.GraphicsLib;
import prefuse.util.PrefuseLib;
import prefuse.util.display.DisplayLib;
import prefuse.util.ui.JFastLabel;
import prefuse.util.ui.JSearchPanel;
import prefuse.util.ui.UILib;
import prefuse.visual.AggregateItem;
import prefuse.visual.AggregateTable;
import prefuse.visual.EdgeItem;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.TreeDepthItemSorter;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Visualization for CollabViz based on radial tree
 * 
 * @author Bill
 * 
 */
public class AggregateRadialTree extends Display
{
    // public static final String DATA_FILE = "data/config/radial2.xml";

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public static final String DATA_FILE = "data/config/similarity.xml";

    private static final String TREE = "tree";

    private static final String NODES = "tree.nodes";

    private static final String EDGES = "tree.edges";

    private static final String AGGR = "aggregates";

    private static final String EDGE_DECORATORS = "edgeDeco";

    private static final Schema DECORATOR_SCHEMA = PrefuseLib
            .getVisualItemSchema();
    static {
        DECORATOR_SCHEMA.setDefault( VisualItem.INTERACTIVE, false ); // noninteractive
        DECORATOR_SCHEMA.setDefault( VisualItem.TEXTCOLOR,
                ColorLib.rgba( 100, 100, 100, 200 ) );
        DECORATOR_SCHEMA.setDefault( VisualItem.FONT,
                FontLib.getFont( "Tahoma", 11 ) ); // and not too big
    }

    private static final String linear = "linear";

    private static final String BLUE_EDGE_SET = "_edgeset";

    private LabelRenderer m_nodeRenderer;

    private EdgeRenderer m_edgeRenderer;

    private String m_label = "label";

    private Map<String, Integer> nameIdMapper = new HashMap<String, Integer>();

    private Node getNodeByName( Graph g, String name ) {
        int nodeId = nameIdMapper.get( name );
        return g.getNode( nodeId );
    }

    public AggregateRadialTree( final Graph g, final String label,
            final int width, final int height ) {
        super( new Visualization() );

        createNameIdMapper( g );

        m_label = label;

        // -- set up visualization --
        m_vis.add( TREE, g );
        m_vis.setInteractive( EDGES, null, true );

        // -- set up renderers --
        m_nodeRenderer = new LabelRenderer( m_label );
        m_nodeRenderer.setRenderType( AbstractShapeRenderer.RENDER_TYPE_FILL );
        m_nodeRenderer.setHorizontalAlignment( Constants.CENTER );
        m_nodeRenderer.setRoundedCorner( 8, 8 );
        m_edgeRenderer = new EdgeRenderer();

        DefaultRendererFactory rf = new DefaultRendererFactory( m_nodeRenderer );
        rf.add( new InGroupPredicate( EDGES ), m_edgeRenderer );

        Renderer polyR = new PolygonRenderer( Constants.POLY_TYPE_CURVE );
        ((PolygonRenderer) polyR).setCurveSlack( 0.15f );

        rf.add( "ingroup('aggregates')", polyR );
        rf.add( new InGroupPredicate( EDGE_DECORATORS ), new LabelRenderer(
                "attribute" ) );

        m_vis.setRendererFactory( rf );

        // add edge labels
        DECORATOR_SCHEMA.setDefault( VisualItem.TEXTCOLOR, ColorLib.gray( 0 ) );

        // add the decorator
        // removed for now, does not display right beside the hovered item
        // m_vis.addDecorators( EDGE_DECORATORS, EDGES, new HoverPredicate(),
        // DECORATOR_SCHEMA );

        // m_vis.addDecorators( EDGE_DECORATORS, EDGES, DECORATOR_SCHEMA );

        initTestData();
        // ////////////////////////////////
        // ///// aggregate stuff
        // ////////////////////////////////
        // draw the nodes as basic shapes

        // -- set up processing actions --

        // colors
        // ItemAction nodeColor = new NodeColorAction( NODES );
        int[] palette = new int[] { ColorLib.rgb( 255, 180, 180 ),
                ColorLib.rgb( 190, 190, 255 ) };
        DataColorAction nodeColor = new DataColorAction( NODES, "type",
                Constants.NOMINAL, VisualItem.FILLCOLOR, palette );

        ItemAction textColor = new TextColourAction( NODES );
        ItemAction edgeColor = new EdgeColourAction( EDGES );

        m_vis.putAction( "textColor", textColor );

        FontAction fonts = new FontAction( NODES,
                FontLib.getFont( "Tahoma", 10 ) );
        fonts.add( "ingroup('_focus_')", FontLib.getFont( "Tahoma", 11 ) );

        // aggr stuff
        ColorAction aStroke = new ColorAction( AGGR, VisualItem.STROKECOLOR );
        aStroke.setDefaultColor( ColorLib.gray( 200 ) );
        aStroke.add( "_hover", ColorLib.rgb( 255, 100, 100 ) );

        palette = new int[] { ColorLib.rgba( 255, 200, 200, 150 ),
                ColorLib.rgba( 200, 255, 200, 150 ),
                ColorLib.rgba( 200, 200, 255, 150 ) };
        ColorAction aFill = new DataColorAction( AGGR, "id", Constants.NOMINAL,
                VisualItem.FILLCOLOR, palette );
        // ///////////

        // recolor
        ActionList recolor = new ActionList();
        recolor.add( nodeColor );
        recolor.add( edgeColor );
        recolor.add( textColor );
        m_vis.putAction( "recolor", recolor );

        // repaint
        ActionList repaint = new ActionList();
        repaint.add( recolor );
        repaint.add( new RepaintAction() );
        m_vis.putAction( "repaint", repaint );

        // animate paint change
        ActionList animatePaint = new ActionList( 400 );
        animatePaint.add( new ColorAnimator( NODES ) );
        animatePaint.add( new RepaintAction() );
        m_vis.putAction( "animatePaint", animatePaint );

        // create the tree layout action
        RadialTreeLayout treeLayout = new RadialTreeLayout( TREE );
        treeLayout.setAutoScale( false );
        treeLayout.setRadiusIncrement( 75 );
        // treeLayout.setAngularBounds(-Math.PI/2, Math.PI);
        m_vis.putAction( "treeLayout", treeLayout );

        CollapsedSubtreeLayout subLayout = new CollapsedSubtreeLayout( TREE );
        m_vis.putAction( "subLayout", subLayout );

        DataSizeAction edgeSize = new DataSizeAction( EDGES, "weight" );

        // create the filtering and layout
        ActionList filter = new ActionList();
        filter.add( new AggregateLayout( AGGR ) );
        filter.add( new TreeRootAction( TREE, null, null, null ) );
        filter.add( fonts );
        filter.add( treeLayout );
        filter.add( subLayout );
        filter.add( textColor );
        filter.add( nodeColor );
        filter.add( edgeColor );
        filter.add( edgeSize );
        filter.add( aStroke );
        filter.add( aFill );
        m_vis.putAction( "filter", filter );

        // animated transition
        ActionList animate = new ActionList( 1250 );
        animate.setPacingFunction( new SlowInSlowOutPacer() );
        animate.add( new QualityControlAnimator() );
        animate.add( new VisibilityAnimator( TREE ) );
        animate.add( new PolarLocationAnimator( NODES, linear ) );
        animate.add( new ColorAnimator( NODES ) );
        animate.add( new RepaintAction() );
        m_vis.putAction( "animate", animate );
        m_vis.alwaysRunAfter( "filter", "animate" );

        // hide far nodes/edges
        ActionList hide = new ActionList();
        final GraphDistanceFilter level1 = new GraphDistanceFilter( TREE,
                Visualization.SEARCH_ITEMS, 1 );
        hide.add( level1 );
        m_vis.putAction( "hide", hide );

        // filter and hide
        ActionList filterAndHide = new ActionList();
        final GraphDistanceFilter level1Selected = new GraphDistanceFilter(
                TREE, Visualization.FOCUS_ITEMS, 1 );
        filterAndHide.add( level1Selected );
        filterAndHide.add( filter );
        m_vis.putAction( "filterAndHide", filterAndHide );

        // restore all nodes/edges
        ActionList unhide = new ActionList();
        // distance between each node
        final GraphDistanceFilter levelInf = new GraphDistanceFilter( TREE,
                TREE, 5000 );
        unhide.add( levelInf );
        m_vis.putAction( "unhide", unhide );

        // ------------------------------------------------

        // initialize the display
        setSize( width, height );
        setItemSorter( new TreeDepthItemSorter() );
        addControlListener( new DragControl() );

        // right-mouse-click zoom-to-fit
        addControlListener( new ZoomToFitControl() );

        // left-mouse-click zoom on selected on-focus node
        // XXX: Uncomment later, important
        // addControlListener( new ZoomToFitControl( Visualization.FOCUS_ITEMS,
        // 150, 0, Control.LEFT_MOUSE_BUTTON ) );

        addControlListener( new ZoomControl() );
        addControlListener( new PanControl() );

        // focus control applies to only nodes
        FocusControl focusControl = new FocusControl( 1, "filter" );
        focusControl.setFilter( new InGroupPredicate( NODES ) );
        addControlListener( focusControl );

        // XXX: Uncomment later
        // use filter and hide to show directly connected node
        // addControlListener( new FocusControl( 1, "filterAndHide" ) );

        addControlListener( new HoverActionControl( "repaint" ) );
        addControlListener( new ToolTipControl( "attribute" ) );

        // ------------------------------------------------

        // filter graph and perform layout
        m_vis.run( "filter" );

        // maintain a set of items that should be interpolated linearly
        // this isn't absolutely necessary, but makes the animations nicer
        // the PolarLocationAnimator should read this set and act accordingly
        m_vis.addFocusGroup( linear, new DefaultTupleSet() );
        m_vis.addFocusGroup( BLUE_EDGE_SET, new DefaultTupleSet() );
        m_vis.getGroup( Visualization.FOCUS_ITEMS ).addTupleSetListener(
                new TupleSetListener()
                {
                    public void tupleSetChanged( TupleSet t, Tuple[] add,
                            Tuple[] rem ) {
                        TupleSet linearInterp = m_vis.getGroup( linear );
                        if( add.length < 1 )
                            return;
                        linearInterp.clear();
                        Node n = (add[0] instanceof Node) ? (Node) add[0]
                                : null;
                        for( ; n != null; n = n.getParent() )
                            linearInterp.addTuple( n );
                    }
                } );

        SearchTupleSet search = new PrefixSearchTupleSet( false );
        m_vis.addFocusGroup( Visualization.SEARCH_ITEMS, search );
        search.addTupleSetListener( new TupleSetListener()
        {
            public void tupleSetChanged( TupleSet t, Tuple[] add, Tuple[] rem ) {
                m_vis.cancel( "animatePaint" );
                // update tree nodes here...
                if( t == null || t.getTupleCount() == 0 ) {
                    m_vis.run( "unhide" );
                } else {
                    m_vis.run( "hide" );
                }
                m_vis.run( "recolor" );
                m_vis.run( "animatePaint" );
                // m_vis.run( "filter" );
            }
        } );
    }

    private void initTestData() {
        AggregateTable at = m_vis.addAggregates( AGGR );
        at.addColumn( VisualItem.POLYGON, float[].class );
        at.addColumn( "id", int.class );
        TupleSet nodes = m_vis.getGroup( NODES );
        Iterator<Tuple> it = nodes.tuples();
        while( it.hasNext() ) {
            int i = 0;
            Node node = (Node) it.next();
            AggregateItem aItem = (AggregateItem) at.addItem();
            aItem.setInt( "id", i++ );
            aItem.addItem( (VisualItem) node );
        }
    }

    /**
     * this starts at 0...
     * 
     * @param g
     *            graph
     */
    private void createNameIdMapper( Graph g ) {
        for( int i = 0; i < g.getNodeCount(); i++ ) {
            Node n = g.getNode( i );
            nameIdMapper.put( n.getString( "name" ), i );
        }
        int i = 0;
        i++;
    }

    public void resizeAndRun( int width, int height ) {
        zoomToFit();
        setSize( width, height );
        m_vis.run( "recolor" );
        m_vis.run( "animatePaint" );
    }

    public void externalSetFocus( String nodeName ) {
        Graph g = (Graph) m_vis.getGroup( TREE );
        Node f = getNodeByName( g, nodeName );
        if( f != null && g.containsTuple( f ) ) {
            m_vis.getGroup( Visualization.FOCUS_ITEMS ).setTuple( f );
            // run filter after setting focus item, this is the same as a
            // "focus action"
            m_vis.run( "filter" );
        }
    }

    private void zoomToFit() {
        long duration = 10;
        int margin = 5;

        Visualization vis = getVisualization();
        Rectangle2D bounds = vis.getBounds( TREE );
        GraphicsLib.expand( bounds, margin + (int) (1 / getScale()) );
        DisplayLib.fitViewToBounds( this, bounds, duration );
    }

    // ----------------------------------------------------------------
    // ----------------------------Factories---------------------------
    // ----------------------------------------------------------------

    public static JPanel demo() {
        return demo( DATA_FILE, "name", 600, 600 );
    }

    public static JPanel demo( String datafile, final String label,
            final int width, final int height ) {
        Graph g = null;
        try {
            g = new GraphMLReader().readGraph( datafile );
        } catch( Exception e ) {
            e.printStackTrace();
            System.exit( 1 );
        }
        return demo( g, label, width, height );
    }

    public static JPanel demo( Graph g, final String label, final int width,
            final int height ) {
        // create a new radial tree view
        final AggregateRadialTree gview = new AggregateRadialTree( g, label,
                width, height - 30 );
        Visualization vis = gview.getVisualization();

        // create a search panel for the tree map
        SearchQueryBinding sq = new SearchQueryBinding(
                (Table) vis.getGroup( NODES ), label,
                (SearchTupleSet) vis.getGroup( Visualization.SEARCH_ITEMS ) );
        JSearchPanel search = sq.createSearchPanel();
        search.setShowResultCount( true );
        search.setBorder( BorderFactory.createEmptyBorder( 5, 5, 4, 0 ) );
        search.setFont( FontLib.getFont( "Tahoma", Font.PLAIN, 11 ) );

        final JFastLabel title = new JFastLabel( "Attributes" );
        title.setPreferredSize( new Dimension( 350, 20 ) );
        title.setVerticalAlignment( SwingConstants.BOTTOM );
        title.setBorder( BorderFactory.createEmptyBorder( 3, 0, 0, 0 ) );
        title.setFont( FontLib.getFont( "Tahoma", Font.PLAIN, 16 ) );

        final Box box = new Box( BoxLayout.X_AXIS );
        box.add( Box.createHorizontalStrut( 10 ) );
        box.add( title );
        box.add( Box.createHorizontalGlue() );
        box.add( search );
        box.add( Box.createHorizontalStrut( 3 ) );

        final JPanel panel = new JPanel( new BorderLayout() );
        panel.add( gview, BorderLayout.CENTER );
        panel.add( box, BorderLayout.SOUTH );

        Color BACKGROUND = Color.WHITE;
        Color FOREGROUND = Color.DARK_GRAY;
        UILib.setColor( panel, BACKGROUND, FOREGROUND );

        gview.addControlListener( new ControlAdapter()
        {
            public void itemEntered( VisualItem item, MouseEvent e ) {
                // if this is an edge
                if( item instanceof EdgeItem ) {
                    title.setText( "Attributes: "
                            + item.getString( "attribute" ) );
                    return;
                }
                if( item.canGetString( label ) )
                    title.setText( item.getString( label ) );
            }

            public void itemExited( VisualItem item, MouseEvent e ) {
                title.setText( null );
            }
        } );

        return panel;
    }

    //
    //
    // ----------------------------------------------------------------
    // ---------------------- MAIN RUNNER -----------------------------
    // ----------------------------------------------------------------
    //
    //
    public static void main( String argv[] ) {
        String infile = DATA_FILE;
        String label = "name";

        if( argv.length > 1 ) {
            infile = argv[0];
            label = argv[1];
        }

        UILib.setPlatformLookAndFeel();

        JFrame frame = new JFrame(
                "i n f o v i z | r a d i a l v i e w - d e m o" );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setContentPane( demo( infile, label, 600, 600 ) );
        frame.pack();
        frame.setVisible( true );
    }

} // end of class RadialGraphView
